package edu.chalmers.AV_2DPlatformerFramework.Utility;

/**
 * Utility class providing various algebraic functionality working on
 * {@code Vector2D}s.
 * @version 1.2
 * @author Joakim Karlsson, Gustav Vidhög
 * @revised Gustav Vidhög, 20140424
 * @revised Joakim Karlsson, Gustav Vidhög, 20140501
 */
public class VectorUtility {
    
    /**
     * Vector addition.
     * @param v1 - first {@code Vector2D} to be added.
     * @param v2 - second {@code Vector2D} to be added.
     * @return the sum of {@code v1} and {@code v2}.
     * @author Joakim Karlsson, Gustav Vidhög
     * @version 1.0
     * @since 1.0
     */
    public static Vector2D add(Vector2D v1, Vector2D v2) {
        return new Vector2D(v1.getX() + v2.getX(), v1.getY() + v2.getY());
    }
    
    /**
     * Vector subtraction.
     * @param v1 - {@code Vector2D} to be subtracted from.
     * @param v2 - {@code Vector2D} to subtract from {@code v1}.
     * @return difference between {@code v1} and {@code v2}.
     * @author Gustav Vidhög
     * @version 1.0
     * @since 1.1
     */
    public static Vector2D subtract(Vector2D v1, Vector2D v2) {
        return new Vector2D(v1.getX() - v2.getX(), v1.getY() - v2.getY());
    }
    
    /**
     * Scalar multiplication.
     * @param v - {@code Vector2D} to be scaled.
     * @param c - scalar to scale {@code Vector2D} with.
     * @return scaled {@code Vector2D}.
     * @author Gustav Vidhög
     * @version 1.0
     * @since 1.1
     */
    public static Vector2D scale(Vector2D v, double c) {
        return new Vector2D(c * v.getX(), c * v.getY());
    }
    
    /**
     * Euclidean norm.
     * @param v - {@code Vector2D} for which norm should be calculated.
     * @return the norm of {@code v}.
     * @author Joakim Karlsson, Gustav Vidhög
     * @version 1.0
     * @since 1.0
     */
    public static double norm(Vector2D v) {
        return Math.sqrt(v.getX() * v.getX() + v.getY() * v.getY());
    }
    
    /**
     * Computes unit vector in direction of a {@code Vector2D}.
     * @param v - non-null {@code Vector2D} to be normalized.
     * @return unit vector in direction of {@code Vector2D}.
     * @throws IllegalArgumentException if {@code v} is the null vector.
     * @author Gustav Vidhög
     * @version 1.0
     * @since 1.1
     */
    public static Vector2D normalize(Vector2D v) {
        if (isNull(v)) {
            throw new IllegalArgumentException(
                    "edu.chalmers.AV_2DPlatformerFramework.VectorUtility." +
                            "normalize: Can not normalize null vector.");
        } else {
            double n = norm(v);
            return scale(v, 1/n);
        }
    }
    /**
     * Computes unit vector normal to a {@code Vector2D}.
     * @param v - non-null {@code Vector2D} to find normal vector to.
     * @return unit vector normal to {@code Vector2D}.
     * @throws IllegalArgumentException if {@code v} is the null vector.
     * @author Joakim Karlsson, Gustav Vidhög
     * @version 1.0
     * @since 1.2
     */
    public static Vector2D normal(Vector2D v) {
        if (isNull(v)) {
            throw new IllegalArgumentException(
                    "edu.chalmers.AV_2DPlatformerFramework.VectorUtility." +
                            "normal: Can not find unit vector normal to null" +
                            " vector.");
        } else {
            return normalize(new Vector2D(v.getY(), -v.getX()));
        }
    }
    
    /**
     * Scalar product.
     * @param v1 - first {@code Vector2D} to calculate scalar product from.
     * @param v2 - second {@code Vector2D} to calculate scalar product from.
     * @return the scalar product between {@code v1} and {@code v2}.
     * @author Gustav Vidhög
     * @version 1.0
     * @since 1.1
     */
    public static double scalar (Vector2D v1, Vector2D v2) {
        return (v1.getX() * v2.getX() + v1.getY() * v2.getY());
    }
    
    /**
     * Orthogonal projection of a {@code Vector2D} onto another.
     * @param v1 - {@code Vector2D} to project.
     * @param v2 - non-null {@code Vector2D} onto which {@code v1} is to be
     * projected.
     * @return orthogonal projection of {@code v1} onto {@code v2}.
     * @throws IllegalArgumentException if {@code v2} is the null vector.
     * @author Gustav Vidhög
     * @version 1.0
     * @since 1.1
     */
    public static Vector2D project(Vector2D v1, Vector2D v2) {
        if (isNull(v2)) {
            throw new IllegalArgumentException(
                    "edu.chalmers.AV_2DPlatformerFramework.VectorUtility." +
                            "project: Can not project onto null vector.");
        } else {
            return scale(v2, scalar(v1, v2) / scalar(v2, v2));
        }
    }
    
    /**
     * Reflection of a (@code Vector2D) in another.
     * @param v1 - {@code Vector2D} to reflect.
     * @param v2 - non-null {@code Vector2D} in which {@code v1} is to be
     * reflected.
     * @return reflection of (@code v1) in (@code v2).
     * @throws IllegalArgumentException if {@code v2} is the null vector.
     * @author Joakim Karlsson, Gustav Vidhög
     * @version 1.0
     * @since 1.2
     */
    public static Vector2D reflect(Vector2D v1, Vector2D v2) {
        if (isNull(v2)) {
            throw new IllegalArgumentException(
                    "edu.chalmers.AV_2DPlatformerFramework.VectorUtility." +
                            "reflect: Can not reflect in null vector.");
        } else {
            return subtract(scale(project(v1, v2), 2), v1);
        }
    }
    
    /**
     * Test whether {@code Vector2D} is the null vector.
     * @param v - {@code Vector2D} to test.
     * @return true if {@code v} is the null vector; false otherwise.
     * @author Gustav Vidhög
     * @version 1.0
     * @since 1.1
     */
    public static boolean isNull(Vector2D v) {
        return (v.getX() == 0 && v.getY() == 0);
    }
    
}
